<template>
  <div class="relative inset-0">
    <div
      class="loading-container absolute opacity-0 transition-opacity"
      :class="[fullscreen ? 'full-screen' : '', startFromInit ? 'opacity-1': '', transitionDuration]"
      :style="{
        '--loading-color': loadingColor,
        backgroundColor: `${showMask ? maskColor : 'transparent'}`,
        width: `${positionConfig.width}px`,
        height: `${positionConfig.height}px`,
        top: `${positionConfig.top}px`,
        left: `${positionConfig.left}px`,
        opacity: `${isLoading ? '1' : '0'}`,
        pointerEvents: `${pointerEvents}`
      }">
      <div
        class="loading-box w-10rpx h-25rpx relative"
        :style="{animationPlayState: `${isLoading ? 'running' : 'pause'}`}">
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import {useSystemStore} from '@/store'
  import type { PositionConfig } from '@/components/loading';
  import NodeInfo = UniNamespace.NodeInfo

  interface LoadingProps {
    parentClass?: string //父节点的样式名，当需要局部显示Loading时需设置该属性，亦可使用startLoading()方法传入参数手动设置
    fullscreen?: boolean //是否全屏显示Loading，默认为false，优先度排行：startLoading()自定义参数 > fullscreen > parentClass
    showMask?: boolean //是否显示Loading背景，默认为true
    maskColor?: string //Loading背景颜色，默认为白色，仅当showMask为true时生效
    loadingColor?: string //Loading主体颜色，默认为'#1296db'
    allowTouch?: boolean //是否允许触摸穿透遮罩层，默认为false
    startFromInit?: boolean //是否在加载时就启用loading
  }

  const props = withDefaults(defineProps<LoadingProps>(), {
    fullscreen: false,
    showMask: true,
    maskColor: '#ffffff',
    loadingColor: '#1296db',
    allowTouch: false,
    startFromInit: false
  })

  //loading容器位置尺寸配置信息
  const positionConfig = ref<PositionConfig>({
    width: 0,
    height: 0,
    top: 0,
    left: 0
  })

  const systemStore = useSystemStore()
  const {windowWidth, windowHeight, navigationHeight} = storeToRefs(systemStore)
  const isLoading = ref(props.startFromInit) //loading状态
  const loadingColor = ref(props.loadingColor) //映射props中的loadingColor
  const maskColor = ref(props.maskColor) //映射props中的maskColor
  const transitionDuration = ref('duration-0')

  /**
   * 开始Loading动画
   * @param positionOptions 手动配置的位置尺寸信息，参数可选，为空时必须设置组件属性fullscreen或parentClass
   */
  function startLoading(positionOptions: PositionConfig | null = null): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      if (positionOptions) {
        positionConfig.value = {
          width: positionOptions.width || 0,
          height: positionOptions.height || 0,
          top: positionOptions.top || 0,
          left: positionOptions.left || 0
        }
        !isLoading.value && (isLoading.value = true)
        resolve('开始Loading')
      } else {
        if (props.fullscreen) {
          !isLoading.value && (isLoading.value = true)
          resolve('开始Loading')
        } else if (!!props.parentClass) {
          uni.getSystemInfo({
            success: () => {
              const obj = uni.createSelectorQuery().select(`.${props.parentClass}`)
              obj.boundingClientRect(parentData => {
                if (!!parentData) {
                  const {width, height, top, left} = parentData as NodeInfo
                  if (Object.getOwnPropertyNames(parentData).length) {
                    positionConfig.value = Object.assign(positionConfig.value, parentData)
                  }
                  positionConfig.value.top = 0
                  if ([width, height, top, left].every(item => typeof item === 'number')) {
                    if ((height as number) > windowHeight.value - (top as number)) {
                      positionConfig.value.height = windowHeight.value - (top as number) / 2 - navigationHeight.value
                    }
                    if ((width as number) > windowWidth.value - (left as number)) {
                      positionConfig.value.width = windowWidth.value - (left as number) / 2
                    }
                    positionConfig.value.width < 80 && (positionConfig.value.width = 45)
                    positionConfig.value.height < 80 && (positionConfig.value.height = 45)
                    !isLoading.value && (isLoading.value = true)
                    resolve('开始Loading')
                  } else {
                    reject('Loading Error: Fail to get the value of bounding client rect.')
                  }
                } else {
                  reject(`Loading Error: Unable to query a node that matches the selector '.${props.parentClass}'.`)
                }
              }).exec()
            },
            fail: err => {
              reject(err)
            }
          })
        }
      }
    })
  }

  //结束loading
  function stopLoading(): Promise<string> {
    return new Promise<string>((resolve, reject) => {
      if (isLoading.value) {
        isLoading.value = false
        resolve('结束Loading')
      } else {
        resolve('Loading Error: Cannot stop loading because the loading animation is not running.')
      }
    })
  }

  defineExpose({startLoading, stopLoading, isLoading})

  //计算是否允许触摸穿透
  const pointerEvents = computed((): 'none' | 'all' => {
    const canTouch = props.allowTouch ? true : !isLoading.value
    return canTouch ? 'none' : 'all'
  })

  onMounted(() => {
    transitionDuration.value = 'duration-300'
  })
</script>

<style scoped lang="scss">
  @import 'loading';

  $loading-color: v-bind(loadingColor);
  $mask-color: v-bind(maskColor);

  .loading-container {
    background-color: $mask-color;

    .loading-box {
      background-color: $loading-color;
      animation-delay: 0.4s;
      animation: loading-animation 1s ease-in-out infinite;
    }

    .loading-box:before {
      animation-delay: 0.2s;
    }

    .loading-box:after {
      animation-delay: 0.6s;
    }

    @keyframes loading-animation {
      0%, 100% {
        box-shadow: 0 0 0 $loading-color, 0 0 0 $loading-color;
      }
      50% {
        box-shadow: 0 -15rpx 0 $loading-color, 0 -15rpx 0 $loading-color;
      }
    }
  }
</style>